
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <set>

using namespace std;

#include "def.h"
#include "BiContainer.h"
#include "Mutex.h"
#include "Cond.h"
#include "Lockable.h"
#include "VarCol.h"
#include "Formattable.h"

Formattable::Formattable (const string& table_name) {
 formatModified = false;
 tableName = table_name;
 formatName = table_name + ".frm";
 numColumns = 0;
 rowLength = 0;
}

bool Formattable::readFormat() {
 ifstream format;

 try {
  format.open(formatName.c_str(), ifstream::in);
 }
 catch (...) {
  cerr << "Unable to open format file for table " << formatName << endl;
  return false;
 }

 /* read number of pages field */
 format >> numPages;
 format.seekg (PAGE_COUNT_LENGTH - format.tellg(), ios_base::cur); ); /* eat up remaining '\0' 
                                                                                  characters */

 /* create TablePage objects */
 TablePage page;
 for (int i = 0; i < numPages; i++) {
  page.set (tableName, i);
  pages.push_back ( page );
 }

 /* read format for each colummn */
 int tmpInt;
 int prePos;
 string tmpStr;

 VarCol varCol;

 while ( !format.eof() ) {
  try {
   /* column type */
   prePos = format.tellg ();
   format >> tmpInt;
   format.seekg (COLUMN_TYPE_LENGTH - format.tellg() + prePos, ios_base::cur); /* eat up remaining '\0' 
                                                                                  characters */

   if (format.eof()) {
    break;
   }

   removedColumns.push_back ( false );

   colType.push_back ( tmpInt );

   /* column length */
   prePos = format.tellg ();
   format >> tmpInt;
   format.seekg (COLUMN_LENGTH_LENGTH - format.tellg() + prePos, ios_base::cur);

   colLen.push_back ( tmpInt );

   rowLength += tmpInt;

   tmpStr.resize (COLUMN_NAME_LENGTH);
   format.read (&tmpStr[0], COLUMN_NAME_LENGTH);

   size_t nullIndex = tmpStr.find('\0'); /* remove trailing placeholder \0 characters */
   if (nullIndex < COLUMN_NAME_LENGTH) tmpStr.erase( nullIndex );

   colName.insert( pair<string, int> ( tmpStr, numColumns ) );
   colNameIndex.push_back (tmpStr);

   if (colType[numColumns] == TYPE_VAR_INTEGER || colType[numColumns] == TYPE_VAR_STRING) { /* Variable-length column */
    varCol.set (tableName, tmpStr);
    varCols.insert ( pair<int, VarCol> (numColumns, varCol) );
   }

  }   
  catch (bad_alloc&) {
   cerr << "Insufficient memory" << endl;
   return false;
  }
  catch (bad_exception&) {
   cerr << "Format for column " << (numColumns + 1) << " is corrupted!" << endl;
   return false;
  }

  tmpStr.clear();
  numColumns++;

  if ( format.peek() == EOF ) {
   break;
  }
 }


 format.close();
 return true;
}

bool Formattable::saveNumPages () {
 ofstream format;

 try {
  format.open(formatName.c_str(), ofstream::out | ofstream::trunc);
 }
 catch (...) {
  cerr << "Unable to open format file for table " << formatName << endl;
  return false;
 }

 format.fill ('\0');
 format.flags(ios::left);

 /* save number of pages field */
 format.width (PAGE_COUNT_LENGTH);
 format << numPages;
 
 format.close ();
}

bool Formattable::saveFormat() {
 ofstream format;

 try {
  format.open(formatName.c_str(), ofstream::out | ofstream::trunc);
 }
 catch (...) {
  cerr << "Unable to open format file for table " << formatName << endl;
  return false;
 }

 format.fill ('\0');
 format.flags(ios::left);

 /* save number of pages field */
 format.width (PAGE_COUNT_LENGTH);
 format << numPages;

 vector<string>::iterator nameItr = colNameIndex.begin();

 vector<int>::iterator lenItr = colLen.begin();
 vector<int>::iterator lenItrEnd = colLen.end();

 vector<int>::iterator typeItr = colType.begin();

 while (lenItr < lenItrEnd) {
  try {
   format.width(COLUMN_TYPE_LENGTH);
   format << *typeItr;
   format.width(COLUMN_LENGTH_LENGTH);
   format << *lenItr;
   format.width(COLUMN_NAME_LENGTH);
   format << *nameItr << endl;
  }
  catch (...) {
   cerr << "Could not save format" << endl;
  }

  nameItr++;
  lenItr++;
  typeItr++;
 }

 format.close();
 return true;
}

bool Formattable::setColumnFormat (const string& name, int type, int len) {
 int index = getColumnIndex(name);
 
 if (index == -1) {
  cout << "not found" << endl;
  return false;
 }
 
 beginFormatModification ();

 formatModMutex.lock ();

 if (type != 0) {
  if ((colTypeMod[index] == TYPE_VAR_INTEGER || colTypeMod[index] == TYPE_VAR_STRING)
        && (type != TYPE_VAR_INTEGER && type != TYPE_VAR_STRING)) {
   /* if we are switching from a variable-length column to a normal one  */
   varColsMod.erase ( index );   
  }
  else if ((type == TYPE_VAR_INTEGER || type == TYPE_VAR_STRING)
        && (colTypeMod[index] != TYPE_VAR_INTEGER && colTypeMod[index] != TYPE_VAR_STRING)) { 
   /* if we are switching from a normal column to a variable-length one  */
   VarCol varCol;
   varCol.set (tableName, colNameIndexMod[index]);
   varColsMod.insert ( pair<int, VarCol> (index, varCol) );
  }

  colTypeMod[index] = type;
 }

 if (len != 0) {  
  rowLengthMod -= colLenMod[index];
  colLenMod[index] = len;
  rowLengthMod += len;
 }

 formatModMutex.unlock ();

 return true;
}

bool Formattable::setColumnName(const string& name, const string& new_name) {
 int index = getColumnIndex(name);
 
 if (index != -1) {
  beginFormatModification ();

  formatModMutex.lock ();

  colNameMod.erase (name);
  colNameMod.insert( pair<string, int> (new_name, index) );
  colNameIndexMod[index] = new_name;

  VarCol* varCol = & (varColsMod[index]); /* avoiding memory copy */
  varCol->set (tableName, new_name);

  formatModMutex.unlock ();
  return true;
 }
 else {
  return false;
 }

}

bool Formattable::addColumn (const string& name, int type, int len) {
 try {
  /* grow vectors */
  beginFormatModification ();
  formatModMutex.lock ();
  colNameIndexMod.resize (numColumnsMod + 1);
  colLenMod.resize (numColumnsMod + 1);
  colTypeMod.resize (numColumnsMod + 1);

  colNameMod.insert ( pair<string, int> (name, numColumnsMod) );
  colNameIndexMod[numColumnsMod] = name; 
  colLenMod[numColumnsMod] = len;
  colTypeMod[numColumnsMod] = type;
  rowLengthMod += len;

  if (type == TYPE_VAR_INTEGER || type == TYPE_VAR_STRING) {
   VarCol varCol;
   varCol.set (tableName, name);
   varColsMod.insert ( pair<int, VarCol> (numColumnsMod, varCol) );
  }

  numColumnsMod ++;

  formatModMutex.unlock ();
 }
 catch (bad_alloc&) {
  cerr << "insufficient memory" << endl;
  return false;
 }
 catch (...) {
  cerr << "Unknown error" << endl;
  return false;
 }
 
 return true;
}

bool Formattable::removeColumn (const string& name) {
 int index = getColumnIndex (name);

 beginFormatModification ();

 formatModMutex.lock ();

 removedColumns[index] = true; /* we will have to discard this column's data in 
                                  commitFormatModification() */

 colNameMod.erase (name);
 colNameIndexMod.erase (colNameIndexMod.begin() + index);
 rowLengthMod -= colLenMod[index];
 colLenMod.erase (colLenMod.begin() + index);

 if (colTypeMod[index] == TYPE_VAR_INTEGER || colTypeMod[index] == TYPE_VAR_STRING) {
  varColsMod.erase ( index );
 }

 colTypeMod.erase (colTypeMod.begin() + index);

 numColumnsMod--;

 formatModMutex.unlock ();

 return true;
}

int Formattable::getColumnIndex(const string& name) {
 map<string, int>::iterator nameItr = colName.find(name);
 
 if (nameItr != colName.end()) {
  return nameItr->second;
 }
 else {
  return -1;
 }
}

string& Formattable::getColumnName (int index) {
 return colNameIndex [index];
}

int Formattable::getColumnType(int index) {
 return colType [index];
}

int Formattable::getColumnLen(int index) {
 return colLen [index];
}

int Formattable::getColumnCount () {
 return numColumns;
}

int Formattable::getRowLength () {
 return rowLength;
}

bool Formattable::isVarCol (int index) {
 return (colType[index] == TYPE_VAR_STRING || colType[index] == TYPE_VAR_INTEGER);
}

void Formattable::beginFormatModification () {
 formatModMutex.lock ();
 if (!formatModified) {
  colNameMod = colName;
  colNameIndexMod = colNameIndex;
  colTypeMod = colType;
  colLenMod = colLen;
  varColsMod = varCols;
  rowLengthMod = rowLength;
  numColumnsMod = numColumns;
  formatModified = true;
 }
 formatModMutex.unlock ();
}

void Formattable::addPage () {
 int i; /* readLock all pages to avoid having to synchronize page containers */ 
 for (i = 0; i < numPages; i++) {
  pages[i].readLock ();
 }

 TablePage page;

 page.set (tableName, i + 1);
 pages.push_back (page);

 numPages++;
 
 int i; /* readUnlock all pages */ 
 for (i = 0; i < numPages; i++) {
  pages[i].readUnlock ();
 }

 numPagesModified = true;
 pageAddCond.broadcast();
}
